﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using IrisGenerator.Common.Modules;
using IrisGenerator.Common.Core.Rules;

namespace IrisGenerator.Common.Core
{
    public static class DatabaseRegistering
    {
        #region Public methods

        /// <summary>
        /// Register modules to the database referential
        /// </summary>
        /// <param name="path">The path the modules should be looked for</param>
        /// <returns>An integer indicating how many modules were registered.</returns>
        public static void RegisterModules(string path,out int updated,out int inserted)
        {

            inserted = 0;
            updated = 0;

            if (Directory.Exists(path))
            {
                string[] files = Directory.GetFiles(path, "*.dll");
                if (files.Count() > 0)
                {
                    ModuleCollection existingModules = IrisGenerator.Common.Modules.Module.GetAll();

                    foreach (string file in files)
                    {
                        ModuleCollection moduleCol = AnalyzeAssembly(file);
                        foreach (IrisGenerator.Common.Modules.Module mod in moduleCol)
                        {
                            if (!existingModules.Exists(mod))
                            {
                                mod.RecursiveInsert();
                                inserted++;
                            }
                            else
                            {
                                mod.ModId = existingModules.Find(mod.ModName).ModId;
                                mod.RecursiveUpdate();
                                updated++;
                            }
                        }
                    }
                }
            }
            else
            {
                throw new ArgumentException("No such path or directory : " + path + ". Cannot register modules.");
            }
            //return returnValue;
        }

        #endregion

        #region Private methods

        #region Modules

        /// <summary>
        /// Analyzes an assembly to get the modules
        /// </summary>
        /// <param name="file">The assembly in which the modules will be looked for</param>
        /// <returns>The modules that were found</returns>
        private static ModuleCollection AnalyzeAssembly(string file)
        {
            ModuleCollection returnValue = new ModuleCollection();

            if (string.IsNullOrEmpty(file))
                return returnValue;

            if (!File.Exists(file))
                return returnValue;

            Assembly ass = null;

            try
            {
                ass = Assembly.LoadFile(file);
            }
            catch (FileLoadException fileEx)
            {
                throw;
            }
            catch (BadImageFormatException badFormatEx)
            {
                throw;
            }

            foreach (Type t in ass.GetTypes())
            {
                if (TypeIsModule(t))
                {
                    IrisGenerator.Common.Modules.Module mod = CreateModuleFromType(t);

                    mod.Target = GetModuleTargets(t);

                    ModuleParameterCollection parameters=GetModuleParameters(t);

                    foreach (ModuleParameter param in parameters)
                    {
                        param.Module = mod;
                        mod.ModuleParameterCollection.Add(param);
                    }
                    
                    returnValue.Add(mod);
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Determines whether a type is a module or not.
        /// </summary>
        /// <param name="type">The type to analyze</param>
        /// <returns>True if the type is a module. False Otherwise</returns>
        private static bool TypeIsModule(Type type)
        {
            if (type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(Modules.ModuleBase)))
                return true;
            return false;
        }

        /// <summary>
        /// Creates a module from a Type.
        /// </summary>
        /// <param name="type">The type from which to create a module</param>
        /// <returns>A module that was created with the attributes specified on the type</returns>
        private static IrisGenerator.Common.Modules.Module CreateModuleFromType(Type type)
        {
            if (type == null)
                return null;

            IrisGenerator.Common.Modules.Module returnValue = new IrisGenerator.Common.Modules.Module();

            object[] objects;

            returnValue.ModAssembly = type.Assembly.FullName;

            objects = type.GetCustomAttributes(typeof(Modules.ModuleNameAttribute), true);

            if(objects!=null && objects.Count()>0 && objects[0]!=null)
                returnValue.ModName=((Modules.ModuleNameAttribute)objects[0]).Name;

            objects = type.GetCustomAttributes(typeof(Modules.ModuleDescriptionAttribute), true);

            if (objects != null && objects.Count() > 0 && objects[0] != null)
                returnValue.ModDescription = ((Modules.ModuleDescriptionAttribute)objects[0]).Description;

            returnValue.ModType = type.FullName;

            return returnValue;

        }

        #endregion

        #region Module Parameters

        /// <summary>
        /// Gets all the ModuleTargetAttributes declared for the type.
        /// </summary>
        /// <param name="type">The type in which ModuleTargetAttributes will be looked for</param>
        /// <returns>A Collection of Targets.</returns>
        private static Target GetModuleTargets(Type type)
        {

            //TODO: Implements target names.

            Target returnValue = new Target();

            if (type == null)
                return returnValue;

            object[] objects;

            objects = type.GetCustomAttributes(typeof(Modules.ModuleTargetAttribute), true);

            if (objects != null && objects.Count() > 0 && objects[0] != null)
            {
                foreach (Modules.ModuleTargetAttribute target in objects)
                {
                    Target tmp;
                    if (!TargetCollection.Exists(target.Code, out tmp))
                    {
                        returnValue = new Target();
                        returnValue.TarCode = target.Code;
                        returnValue.TarDescription = target.Description;

                        returnValue.Insert();
                    }
                    else
                        returnValue = tmp;
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Gets the parameters declared in a module reading the Attribute ModuleParameterAttribute
        /// </summary>
        /// <param name="type">The type where the attributes are to be read</param>
        /// <returns>A collection of parameters attached to the module.</returns>
        private static ModuleParameterCollection GetModuleParameters(Type type)
        {
         
            ModuleParameterCollection returnValue = new ModuleParameterCollection();

            if (type == null)
                return returnValue;

            object[] objects;

            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                objects = property.GetCustomAttributes(typeof(Modules.ModuleParameterAttribute), true);

                if (objects != null && objects.Count() > 0)
                {
                    foreach (Modules.ModuleParameterAttribute parameter in objects)
                    {
                        returnValue.Add(parameter.ModuleParameter);
                    }
                }
            }

            //objects=type.GetCustomAttributes(typeof(Modules.ModuleParameterAttribute),true);

            

            return returnValue;

        }

        #endregion

        #endregion

    }
}
